package br.mikhas.db.model.factory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

import br.mikhas.db.model.Field;
import br.mikhas.db.model.Model;
import br.mikhas.reflector.Reflect;
import br.mikhas.reflector.annotation.AnnotationProxy;
import br.mikhas.reflector.field.FieldProxy;
import br.mikhas.util.Bytes;
import br.mikhas.util.TypeUtils;

public class DataBaseModelFactory {

	private static final int DEFAULT_LENGTH = 255;
	private HashMap<Class<? extends Serializable>, Model> models = new HashMap<Class<? extends Serializable>, Model>();

	public Model createModelFor(Class<? extends Serializable> cls) {

		Model model = getModel(cls);

		return model;
	}

	private Model getModel(Class<? extends Serializable> cls) {

		Model model = models.get(cls);

		if (model != null)
			return model;

		DataBaseModel datamodel = new DataBaseModel(cls);
		models.put(cls, datamodel);

		getModelFields(datamodel);

		return datamodel;
	}

	private void getModelFields(DataBaseModel model) {

		FieldProxy[] fields = Reflect.on(model.getClassName()).fields();

		ArrayList<Field> indexFields = new ArrayList<Field>();

		for (FieldProxy field : fields) {
			if (!field.isTransient() && !field.isStatic()) {
				Field modelField = buildField(field);
				model.addField(modelField);

				if (modelField.isIndex()) {
					indexFields.add(modelField);
				}
			}
		}

		if (!indexFields.isEmpty()) {
			DataBaseIndex index = new DataBaseIndex(indexFields);
			int indexLength = calculateLength(indexFields);
			index.setLength(indexLength);
			model.setIndex(index);
		}

		int modelLength = calculateLength(model.getFields());
		model.setLength(modelLength);

	}

	private int calculateLength(Collection<Field> fields) {
		int len = 0;
		for (Field f : fields) {
			len += getLength(f);
		}

		return len;
	}

	@SuppressWarnings("unchecked")
	private int getLength(Field field) {
		Class<?> t = field.getType();
		if (TypeUtils.isWrapper(t))
			t = TypeUtils.getPrimitive(t);

		if (TypeUtils.isPrimitive(t)) {
			return Bytes.getLength(t);
		} else if (t == String.class) {
			return field.getLength() * Bytes.CHAR_LENGTH;
		}

		br.mikhas.db.model.Index index = this.getModel(
				(Class<? extends Serializable>) t).getIndex();

		if (index != null)
			return index.getLength();
		else {
			throw new ModelFactoryException(
					"The model for class "
							+ t
							+ " cannot be referentied because it does not have an index");
		}
	}

	/**
	 * Build the field model according to annotaion configuration
	 * 
	 * @param fp
	 *            {@link FieldProxy} which helps extracting information from
	 *            class field
	 * @return The field built
	 */
	private Field buildField(FieldProxy fp) {

		DataBaseModelField field = new DataBaseModelField(fp.field());

		if (field.getType() == String.class) {
			AnnotationProxy len = fp.annotation(Length.class);

			if (len != null) {
				Length length = (Length) len.getAnnotation();
				field.setLength(length.value());
			} else {
				field.setLength(DEFAULT_LENGTH);
			}
		}

		AnnotationProxy ann = fp.annotation(Index.class);

		if (ann != null) {
			field.setIndex(true);
			Index index = (Index) ann.getAnnotation();
			field.setSequential(index.sequential());
		} else {
			field.setIndex(false);
			field.setSequential(false);
		}

		return field;
	}
}
